#!/usr/bin/env python
from __future__ import with_statement

import x3d
from xml.etree.ElementTree import Element, ElementTree

def vector_attr(val):
	return ' '.join([str(v) for v in val])
	
def string_vector(val):
	return '"' + '" "'.join([v for v in val]) + '"'

# Python version of ElementTree doesn't include indentation.
#
# From http://effbot.org/zone/element-lib.htm, development home of ElementTree.
# Used under public domain
def indent(elem, level=0):
    i = "\n" + level*"  "
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
        for elem in elem:
            indent(elem, level+1)
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i

class SceneElementTree(ElementTree):
	def __init__(self):
		self.root = Element('X3D', {'version':'3.0', 'profile': 'Immersive',
						'xmlns:xsd':
							'http://www.w3.org/2001/XMLSchema-instance',
						'xsd:noNamespaceSchemaLocation':
							'http://www.web3d.org/specifications/x3d-3.1.xsd'})
		ElementTree.__init__(self, self.root)
		
		head = Element('head')
		head.append(Element('meta', {'name':'generator', 'content':'PyTAMP'}))
		self.root.append(head)
		
	def build(self, scene):
		sceneNode = Element('Scene')
		for o in scene.objs():
			sceneNode.append(self.build_obj(o))
		self.root.append(sceneNode)
		
	def build_obj(self, obj):
		if isinstance(obj, x3d.Shape):
			return self.build_shape(obj)
		elif isinstance(obj, x3d.Transform):
			return self.build_transform(obj)
		elif isinstance(obj, x3d.Group):
			return self.build_group(obj)
		elif isinstance(obj, x3d.Bindable):
			return self.build_bindable(obj)
		else:
			raise RuntimeError("Unrecognized scene object: " + str(obj))
			
	def build_shape(self, shape):
		node = Element('Shape')
		if shape.appearance():
			node.append(self.build_appearance(shape.appearance()))
		if shape.geometry():
			node.append(self.build_geometry(shape.geometry()))
		return node
		
	def build_bindable(self, bind):
		node = None
		if isinstance(bind, x3d.Viewpoint):
			node = self.build_viewpoint(bind)
		else:
			raise RuntimeError("Unrecognized bindable: " + str(bind))
		if bind.bind():
			node.set('set_bind', 'true')
		return node
		
	def build_viewpoint(self, vp):
		attrs = {}
		if vp.center_rotation():
			attrs['centerOfRotation'] = vector_attr(vp.center_rotation())
		if vp.position():
			attrs['position'] = vector_attr(vp.position())
		if vp.orientation():
			attrs['orientation'] = vector_attr(vp.orientation())
		if vp.jump() != None:
			attrs['jump'] = 'true' if vp.jump() else 'false'
		if vp.fov():
			attrs['fieldOfView'] = str(vp.fov())
		node = Element('Viewpoint', attrs)
		return node
		
	def build_transform(self, trans):
		attrs = {}
		if trans.translate():
			attrs['translation'] = vector_attr(trans.translate())
		if trans.rotate():
			attrs['rotation'] = vector_attr(trans.rotate())
		if trans.scale():
			attrs['scale'] = vector_attr(trans.scale())
		if trans.center():
			attrs['center'] = vector_attr(trans.center())
		if trans.scale_orientation():
			attrs['scaleOrientation'] = vector_attr(trans.scale_orientation())
		node = Element('Transform', attrs)
		for c in trans.children():
			node.append(self.build_obj( c ))
		return node
		
	def build_group(self, group):
		node = Element('Group')
		for c in group.children():
			node.append(self.build_obj( c ))
		return node
		
	def build_appearance(self, appear):
		node = Element('Appearance')
		if appear.material():
			node.append(self.build_material(appear.material()))
		if appear.texture():
			node.append(self.build_texture(appear.texture()))
		return node
		
	def build_material(self, mat):
		attrs = {}
		if mat.diffuse():
			attrs['diffuseColor'] = ' '.join([str(d) for d in mat.diffuse()])
		if mat.emissive():
			attrs['emissiveColor'] = ' '.join([str(d) for d in mat.emissive()])
		if mat.transparency():
			attrs['transparency'] = str(mat.transparency())
		node = Element('Material', attrs)
		return node
		
	def build_geometry(self, geom):
		if isinstance(geom, x3d.Box):
			return self.build_box(geom)
		elif isinstance(geom, x3d.Cylinder):
			return self.build_cylinder(geom)
		elif isinstance(geom, x3d.Sphere):
			return self.build_sphere(geom)
		elif isinstance(geom, x3d.Text):
			return self.build_text(geom)
		elif isinstance(geom, x3d.LineSet):
			return self.build_lineset(geom)
		elif isinstance(geom, x3d.IndexedFaceSet):
			return self.build_indexedfaceset(geom)
		else:
			raise RuntimeError("Unrecognized geometry: " + str(geom))
			
	def build_box(self, box):
		attrs = {}
		if box.size():
			attrs['size'] = ' '.join([str(s) for s in box.size()])
		if box.solid() != None:
			attrs['solid'] = 'true' if box.solid() else 'false'
		node = Element('Box', attrs)
		return node
		
	def build_sphere(self, sph):
		attrs = {}
		if sph.radius():
			attrs['radius'] = str(sph.radius())
		if sph.solid() != None:
			attrs['solid'] = 'true' if sph.solid() else 'false'
		return Element('Sphere', attrs)
		
	def build_cylinder(self, cyl):
		attrs = {}
		if cyl.radius():
			attrs['radius'] = str(cyl.radius())
		if cyl.height():
			attrs['height'] = str(cyl.height())
		if cyl.solid() != None:
			attrs['solid'] = 'true' if cyl.solid() else 'false'
		return Element('Cylinder', attrs)
		
	def build_lineset(self, ls):
		attrs = {}
		if ls.vertices():
			attrs['vertexCount'] = str(len(ls.vertices()))
		node = Element('LineSet', attrs)
		if ls.vertices():
			node.append(Element('Coordinate', 
								{'point': ' '.join([vector_attr( p ) \
												for p in ls.vertices()])}))
		if ls.colors():
			node.append(Element('Color',
								{'color': ' '.join([vector_attr( c ) \
												for c in ls.colors()])}))
		return node

	def build_indexedfaceset(self, ifs):
		attrs = {}
		attrs['coordIndex'] = vector_attr(ifs.indices())
		if ifs.colors():
			attrs['colorPerVertex'] = 'true' if ifs.colorPerVertex() else 'false'		
		node = Element('IndexedFaceSet', attrs)
		if ifs.vertices():
			node.append(Element('Coordinate', 
								{'point': ' '.join([vector_attr( p ) \
												for p in ifs.vertices()])}))
		if ifs.colors():
			node.append(Element('Color',
								{'color': ' '.join([vector_attr( c ) \
												for c in ifs.colors()])}))
		return node

		
	def build_text(self, txt):
		attrs = {}
		if txt.strings():
			attrs['string'] = string_vector(txt.strings())
		if txt.lengths():
			attrs['length'] = vector_attr(txt.lengths())
		if txt.max_extent():
			attrs['maxExtent'] = txt.max_extent()
		if txt.solid() != None:
			attrs['solid'] = 'true' if txt.solid() else 'false'
		node = Element('Text', attrs)
		if txt.font_style():
			node.append(self.build_font_style(txt.font_style()))
		return node
		
	def build_font_style(self, fs):
		attrs = {}
		if fs.family():
			attrs['family'] = fs.family()
		if fs.justify():
			attrs['justify'] = fs.justify()
		if fs.style():
			attrs['style'] = fs.style()
		if fs.size():
			attrs['size'] = str(fs.size())
		if fs.spacing():
			attrs['spacing'] = str(fs.spacing())
		node = Element('FontStyle', attrs)
		return node
		
	def write(self, file, encoding="us-ascii"):
		indent(self.root)
		ElementTree.write(self, file, encoding)

class SceneWriter(object):
	def __init__(self, scene=None):
		self.scene = scene
		
	def set_scene(self, scene):
		self.scene = scene
		
	def write(self, fname):
		el = SceneElementTree()
		el.build(self.scene)
		preamble = ["<?xml version='1.0' encoding='UTF-8'?>\n",
					"<!DOCTYPE X3D PUBLIC 'ISO//Web3D//DTD X3D 3.1//EN' ",
					"'http://www.web3d.org/specifications/x3d-3.1.dtd'>\n"]
		with open(fname, 'w') as fout:
			fout.writelines(preamble)
			el.write(fout, encoding='utf-8')
			
			
if __name__ == "__main__":
	scene = x3d.Scene()
	box1 = x3d.Shape()
	box1.set_geometry(x3d.Box())
	box1.set_appearance(x3d.Appearance(x3d.Material([0,0,0.6])))
	boxLabel = x3d.Shape(x3d.Appearance(x3d.Material([1,1,1])),
						x3d.Text(['1']))
	boxLabelTrans = x3d.Transform(translate=[-.25,-.25,1.01])
	boxLabelTrans.append(boxLabel)
	boxGroup = x3d.Group()
	boxGroup.append(box1)
	boxGroup.append(boxLabelTrans)
	boxTrans = x3d.Transform(translate=[1,0,0])
	boxTrans.append(boxGroup)
	sphere1 = x3d.Shape(x3d.Appearance(x3d.Material([0,0.8,0])),
						x3d.Sphere())
	scene.append(boxTrans)
	scene.append(sphere1)
	writer = SceneWriter()
	writer.set_scene(scene)
	writer.write('test_x3d.xml')
